package mazegame;

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.HashMap;

import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;

import mazegame.Constants.Direction;
import mazegame.Constants.ClientStatus;

@SuppressWarnings("serial")
public class PlayerUI extends JFrame {

	public static void main(String[] args) {
		PlayerUI clientUI = new PlayerUI();
		clientUI.displayClientUI();
	}

	private JLabel lblServerName = new JLabel("Hostname");
	private JTextField txtHostIPAddr = new JTextField(5);
	private JLabel lblServerPort = new JLabel("Server Port");
	private JLabel lblMyPort = new JLabel(" My Port");
	private JTextField txtServerPort = new JTextField(5);
	private JTextField txtMyPort = new JTextField(5);
	private JButton btnJoinGame = new JButton("Join Game");
	private JTextArea areaViewLogs = new JTextArea(23, 30);
	private JTextArea areaMazeMap = new JTextArea(16, 20);
	private JLabel lblMapSize = new JLabel("Map Size");
	private JLabel lblTreasureCount = new JLabel("Treasure Number");
	private JLabel lblFirstClient = new JLabel("First Client");
	private JTextField txtMapSize = new JTextField(5);	
	private JTextField txtTreasureCount = new JTextField(5);
	private JCheckBox checkFirstClient = new JCheckBox();
	JPanel panelGameSettingsBottom = new JPanel();
	MazePlayer player = null;

	private ClientUpdateTimer clientUpdateTimer;
	//From Server
	public GameStartTimer startTimer = null;
	public int gameStartWaitTime = 20;
	public int gameStartTickcount;
	public Constants.ServerStatus gameStatus = null;

	public PlayerUI() {
		super("Distributed Maze Game");// Title
		player = new MazePlayer(this);
		txtHostIPAddr.setText("127.0.0.1");
		txtServerPort.setText("2001");
		txtMapSize.setText("5");
		txtTreasureCount.setText("20");
		
		// Add Action Listeners
		btnJoinGame.addActionListener(new ClientBtnListener());		
		areaMazeMap.addKeyListener(new ClientKeyListener());
		checkFirstClient.addItemListener(new ClientItemListener());

	}

	public void displayClientUI() {
		areaViewLogs.setEditable(false);
		areaMazeMap.setEditable(false);
		
		//Add Scroll bar
		JScrollPane scrollAreaViewLog = new JScrollPane(areaViewLogs); 
		scrollAreaViewLog.setBounds(10,60,780,100);
		scrollAreaViewLog.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);

		//First Client Options
		JPanel panelGameSettings = new JPanel();
		JPanel panelGameSettingsTop = new JPanel();
		
		panelGameSettings.setBorder(BorderFactory
				.createTitledBorder("Game Settings"));
		panelGameSettingsTop.add(checkFirstClient);
		panelGameSettingsTop.add(lblFirstClient);
		panelGameSettingsBottom.add(lblMapSize);
		panelGameSettingsBottom.add(txtMapSize);
		panelGameSettingsBottom.add(lblTreasureCount);
		panelGameSettingsBottom.add(txtTreasureCount);
		panelGameSettingsBottom.setVisible(false);
		panelGameSettings.add(panelGameSettingsTop,BorderLayout.WEST);
		panelGameSettings.add(new JPanel(),BorderLayout.CENTER);
		panelGameSettings.add(panelGameSettingsBottom,BorderLayout.EAST);
		
		
		// Game option panel
		JPanel panelGameOptions = new JPanel();		
		panelGameOptions.setBorder(BorderFactory
				.createTitledBorder("Game Options"));
		panelGameOptions.add(lblServerName);
		panelGameOptions.add(txtHostIPAddr);
		panelGameOptions.add(lblServerPort);
		panelGameOptions.add(txtServerPort);
		panelGameOptions.add(lblMyPort);
		panelGameOptions.add(txtMyPort);
		panelGameOptions.add(btnJoinGame);
		

		// View Logs Panel
		JPanel panelViewLog = new JPanel();
		panelViewLog.setBorder(BorderFactory.createTitledBorder("Logs"));
		panelViewLog.add(scrollAreaViewLog);

		// Maze Game Panel
		JPanel panelMazeMap = new JPanel();
		panelMazeMap.setBorder(BorderFactory.createTitledBorder("Maze Map"));
		panelMazeMap.add(areaMazeMap);

		JPanel bottomPanel = new JPanel();
		bottomPanel.setLayout(new FlowLayout());
		bottomPanel.add(panelMazeMap);
		bottomPanel.add(panelViewLog);
		
		setLayout(new FlowLayout());
		add(panelGameSettings);
		add(panelGameOptions);
		add(bottomPanel);

		pack();
		setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		this.setSize(650, 600);
		setVisible(true);

	}

	private class ClientBtnListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {

			if (e.getSource() == btnJoinGame) {// If Join Game is Clicked
				
				setLogs("Join Game Button Clicked. Calling for RMI...");
				
				if (checkFirstClient.isSelected()) {// If First Client

					String name = "MazeServerImpl";
					gameStatus = Constants.ServerStatus.SERVER_IDLE;
					gameStartTickcount = gameStartWaitTime;

					player.setMapSize(Integer.parseInt(txtMapSize.getText()));
					player.setTreasureNum(Integer.parseInt(txtTreasureCount
							.getText()));
					player.setTreasureCount(Integer.parseInt(txtTreasureCount
							.getText()));
					player.setNextPlayerID(1);
					player.setPlayerCount(0);
					player.getMyInfo().host=MazeUtil.getOwnHostName();
					player.getMyInfo().port=2001;
					
					MazeServerInterface stub;
					try {
						stub = (MazeServerInterface) UnicastRemoteObject.exportObject(player, 0);
						player.mystub=stub;
						Registry registry = LocateRegistry.createRegistry(player.getMyInfo().port);
						registry.rebind(name, stub);
						SyncServerAndBackupTimer timer=new SyncServerAndBackupTimer(player);//start sync with backup
						player.getServerInfo().host = player.getMyInfo().host;
						player.getServerInfo().port = player.getMyInfo().port;
						player.setServer(true);
						player.setBackup(false);
						
					} catch (RemoteException e1) {
						e1.printStackTrace();
					}

				}

				if (!player.isGameJoined()) // Check Client already Joined
												// the game
				{
					try {
						String host = txtHostIPAddr.getText();
						int serevrPort = Integer.parseInt(txtServerPort.getText());
						player.mazeServerIntf=player.initiateRemoteMazeServer(host, serevrPort);
						int myPort = Integer.parseInt(txtMyPort.getText());
						player.getMyInfo().port=myPort;
						player.getMyInfo().host = MazeUtil.getOwnHostName();
						String joinGameResult = player.updateJoiningClient();
						setLogs(joinGameResult);
					} catch (Exception e1) {
						setLogs("ERROR: Join client failed");
						e1.printStackTrace();
						return;
					}

					// start the update timer
					clientUpdateTimer = new ClientUpdateTimer(1000,
							PlayerUI.this);

					btnJoinGame.setEnabled(false);					
					// Request focus set to Maze map text area
					areaMazeMap.requestFocusInWindow();

				} else {
					setLogs("Player already Joined..");
				}

			} 
		}
	}

	private class ClientKeyListener implements KeyListener {
		public void keyPressed(KeyEvent e) {

			int keyCode = e.getKeyCode();			
			String playerMoveStatus = "";
			String currentMazeMap = "";
			switch (keyCode) {
			case KeyEvent.VK_UP: {
				setLogs("Player request to move UP");
				playerMoveStatus = player.playerMoveStatus(Constants.Direction.DIR_UP);
				break;
			}
			case KeyEvent.VK_DOWN: {
				setLogs("Player request to move DOWN");
				playerMoveStatus = player.playerMoveStatus(Constants.Direction.DIR_DOWN);
				break;
			}
			case KeyEvent.VK_LEFT: {
				setLogs("Player request to move LEFT");
				playerMoveStatus = player.playerMoveStatus(Constants.Direction.DIR_LEFT);
				break;
			}
			case KeyEvent.VK_RIGHT: {
				setLogs("Player request to move RIGHT");
				playerMoveStatus = player.playerMoveStatus(Constants.Direction.DIR_RIGHT);
				break;
			}
			}
			currentMazeMap = updateMazeMap();
			setLogs(playerMoveStatus);
			setMazeMap(currentMazeMap);

		}

		@Override
		public void keyTyped(KeyEvent e) {
			// Not Implemented

		}

		@Override
		public void keyReleased(KeyEvent e) {
			// Not Implemented

		}

	}
	
	private class ClientItemListener implements ItemListener {
		
		public void itemStateChanged(ItemEvent e) {
			if (e.getStateChange() == ItemEvent.SELECTED){				
				panelGameSettingsBottom.setVisible(true);
			}
			if (e.getStateChange() == ItemEvent.DESELECTED){				
				panelGameSettingsBottom.setVisible(false);
			}
				
			
		}
		
	}

	public void setLogs(String logInfo) {
		if (player.getStatus().equals(Constants.ClientStatus.CLIENT_GAME_RUNNING)) {
			int clientId = player.getClientId();
			String playerLog = "";
			if(player.isServer()){
				playerLog = "[SERVER ID : "+(char)(clientId + (int)'A')+"] : ";
			}else if (player.isBackup()){
				playerLog = "[BACKUP ID : "+(char)(clientId + (int)'A')+"] : ";
			}else{
				playerLog = "[CLIENT ID : "+(char)(clientId + (int)'A')+"] : ";	
			}
			logInfo = playerLog + logInfo;
		}

		areaViewLogs.setText(MazeUtil.log(logInfo, areaViewLogs.getText()));

	}

	public void setMazeMap(String mazeMapPlot) {
		areaMazeMap.setText(mazeMapPlot);
	}

	public void requestUpdate() {

		String currentMazeMap = "";
		String playerMoveStatus = player.playerMoveStatus(null);
		
		if (player.getStatus().equals(Constants.ClientStatus.CLIENT_GAME_STARTING)) {
			setLogs(playerMoveStatus);
		}

		if (player.getStatus().equals(Constants.ClientStatus.CLIENT_GAME_RUNNING)) {
			currentMazeMap = updateMazeMap();
			setMazeMap(currentMazeMap);
			clientUpdateTimer.timer.cancel();
			
		}
	}
	
	//From server
	public void updateGameStartTick() {

		System.out.println("UpdateGameStartTick: gameStatus >>> "+gameStatus);
		System.out.println("UpdateGameStartTick: gameStartTickcount >>> "+gameStartTickcount);

		if (gameStatus == Constants.ServerStatus.SERVER_GAME_STARTING
				&& gameStartTickcount > 0) {
			gameStartTickcount -= 1;
			System.out.println("UpdateGameStartTick SERVER_GAME_STARTING:"+gameStatus);
		} else if (gameStatus == Constants.ServerStatus.SERVER_GAME_STARTING
				&& gameStartTickcount <= 0) {
			startTimer.timer.cancel();
			gameStartTickcount = gameStartWaitTime;
			gameStatus = Constants.ServerStatus.SERVER_GAME_RUNNING;
			System.out.println("UpdateGameStartTick SERVER_GAME_RUNNING:"+gameStatus);
			System.out.println("gameStartTickcount:"+gameStartTickcount);
			setLogs("game started");
		}
	}
	
	public String updateMazeMap()
    {
        String mazeMap = "";
        int mapSize = 0;
		try {
			mapSize = player.getMapSizeRemote();
		} catch (RemoteException e) {			
			e.printStackTrace();
		}
        HashMap<Integer, MazePosition> playerPositions = player.getPlayerPositions();
        HashMap<Integer, Integer> playerTreasureCounts = player.getPlayerTreasureCounts();        
        int clientId = player.getClientId();
        int myTreasureCount = player.getMyTreasureCount();
        HashMap<MazePosition, Integer> treasurePositions = player.getTreasurePositions();
        
        
        String playerInfo = "Other Players \n";
        for (int i=0; i<mapSize; ++i)
        {
            for (int j=0; j<mapSize; ++j)
            {
                if (playerPositions.containsValue(new MazePosition(i, j)))
                {
                    int curId = MazeUtil.getKeyByValue(playerPositions, new MazePosition(i, j));                    		
                    int treasure = playerTreasureCounts.get(curId);
                    
                    if (curId == clientId)
                    {
                        mazeMap = mazeMap + "*" + "\b \b";
                        myTreasureCount = playerTreasureCounts.get(clientId);
                    }
                    else
                    {
                        mazeMap = mazeMap + (char)(curId + (int)'A') + "\b \b";
                        playerInfo = playerInfo + "Player " + (char)(curId + (int)'A') + " owns " + treasure + "\n";
                    }
                }
                else if (treasurePositions.containsKey(new MazePosition(i, j)))
                    mazeMap = mazeMap + treasurePositions.get(new MazePosition(i, j)).toString() + "\b \b";
                else
                    mazeMap += "0\b \b";
            }
            
            mazeMap += "\n";
        }       
        
        return mazeMap + "\n My Status \n Id : "+ (char)(clientId + (int)'A') +"\b \b Treasure Count : " + myTreasureCount + "\n \n"+ playerInfo ;
    }
	
	
	

}
